# Import the necessary libraries.
import signal
import sys
from types import FrameType
from typing import Callable, List, Optional, Tuple, Union

if sys.version_info >= (3, 10):
    from typing import TypeAlias
else:
    from typing_extensions import TypeAlias

SignumType: TypeAlias = Union[
    Callable[[signal.Signals, FrameType], None],
    int,
    signal.Handlers,
    None,
]
SignalHandlerFunction: TypeAlias = Callable[[SignumType, Optional[FrameType]], None]


class ScaleneSignals:
    """
    ScaleneSignals class to configure timer signals for CPU profiling and
    to get various types of signals.
    """

    def __init__(self) -> None:
        # Declare these here, then configure them.
        self.cpu_signal: signal.Signals
        self.cpu_timer_signal: int
        # Configure timer signals using set_timer_signals method (defined below).
        self.set_timer_signals(use_virtual_time=True)
        # Set profiling signals depending upon the platform.
        if sys.platform != "win32":
            self.start_profiling_signal = signal.SIGILL
            self.stop_profiling_signal = signal.SIGBUS
            self.memcpy_signal = signal.SIGPROF
            # Malloc and free signals are generated by include/sampleheap.hpp.
            self.malloc_signal = signal.SIGXCPU
            self.free_signal = signal.SIGXFSZ
        else:
            # Currently, only CPU profiling signals are activated for Windows
            self.start_profiling_signal = None
            self.stop_profiling_signal = None
            self.memcpy_signal = None
            self.malloc_signal = None
            self.free_signal = None

    def set_timer_signals(self, use_virtual_time: bool = True) -> None:
        """
        Set up timer signals for CPU profiling.

        use_virtual_time: bool, default True
            If True, sets virtual timer signals, otherwise sets real timer signals.
        """
        if sys.platform == "win32":
            self.cpu_timer_signal = (
                signal.SIGBREAK
            )  # Note: on Windows, this is unused, so any signal will do
            self.cpu_signal = signal.SIGBREAK
            return
        if use_virtual_time:
            self.cpu_timer_signal = signal.ITIMER_VIRTUAL
            self.cpu_signal = signal.SIGVTALRM
        else:
            self.cpu_timer_signal = signal.ITIMER_REAL
            self.cpu_signal = signal.SIGALRM

    def get_timer_signals(self) -> Tuple[int, signal.Signals]:
        """
        Returns 2-tuple of the integers representing the CPU timer signal and the CPU signal.
        """
        return self.cpu_timer_signal, self.cpu_signal

    def get_lifecycle_signals(self) -> Tuple[signal.Signals, signal.Signals]:
        return (self.start_profiling_signal, self.stop_profiling_signal)

    def get_all_signals(self) -> List[signal.Signals]:
        """
        Return all the signals used for controlling profiling, except the CPU timer.
        """
        return [
            self.start_profiling_signal,
            self.stop_profiling_signal,
            self.memcpy_signal,
            self.malloc_signal,
            self.free_signal,
            self.cpu_signal,
        ]
